feat!: migrate from prost to buffa for protobuf codegen - #557
Merged
Conversation
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migrate protobuf codegen from prost/prost-build to buffa/buffa-build v0.8.1 (from crates.io), then build on top of buffa's zero-copy views to cut allocations on the hot receive/decode paths.
prost/prost-buildwithbuffa/buffa-build(v0.8.1, from crates.io).whatsapp.rsis no longer tracked —build.rsgenerates it intoOUT_DIRfrom the committedwhatsapp.desc(+whatsapp.desc.sha256freshness guard). Consumers never needprotoc.generate_views=true) now used on real decode paths — not just enabled for the future.API surface changes (codebase-wide)
Option<Box<T>>MessageField<T>(.as_option()/.is_set()/.is_unset())Option<i32>Option<EnumType>(typed)CamelCaseSCREAMING_SNAKE_CASEAdvSignedDeviceIdentityADVSignedDeviceIdentityType::decode(bytes)Type::decode_from_slice(bytes)TypeView::decode_view(bytes)/ owned viewsmsg.encode(&mut buf)?msg.encode_to_vec()Nonefields..Default::default()Performance work on top of the migration
compute_sizetraversal in session serialization.Notable fixes / decisions
is_sender_key_distribution_only: slow path encodes-and-compares, because buffa'sMessageFieldequality treats set-to-default as equal to unset.whatsapp.rsremoved from version control;build.rsfails fast on a stale descriptor or missing post-process marker.PollOptionproto workaround dropped — buffa ≥ v0.7 handles the nestedOptionmessage, so the proto is back to upstreammessage Option.Updated against
mainThis branch has been merged up to current
main(through #683). The merge layered the buffa view architecture together withmain's recent work, resolving conflicts by keeping the best of each side:wacore/src/history_sync.rs): kept buffa's zero-copy view extraction as the base, and portedmain's streaming decompression (perf: implement streaming decompression for history sync processing #672) and message-secret retention metadata —timestamp/is_poll_or_event/is_bot_invocation(feat(msg-secret)!: bound messageSecret retention by policy and event-time horizon #668) — onto it (prost-based extraction can't compile here).LazyHistorySync(events.rs): keptmain's memory-freeing design (raw bytes freed after first decode, perf(history-sync): free LazyHistorySync raw bytes after a successful decode #669), adapted to buffa'sdecode_from_slice; dropped the buffaview()accessor (incompatible with freeing, no real consumers).store/device.rs): combinedmain'sArc<…>wrapping (perf(send): Arc immutable device fields + recent-message bytes #674) with buffa'sADVSignedDeviceIdentitynaming +MessageFieldAPI.messages.rs): keptmain'svalidate_bcl_hash/ standard-base64 phash parity (fix(send): correct group phash and mark full SKDM target set (WA Web parity) #678/feat: WA Web phash parity — usync device_hash (#3), group-metadata phash (#7), bcl hash validation (#6) #679) + buffa'sunpadded_message_len/unpad_message_refsplit.src/message.rsreferenced the owned message before it was materialized on the buffa receive path — now reads the borrowed view.Post-migration: binary size, perf regressions, publishability, descriptor guards
After the migration landed green, a measurement-driven cleanup pass:
.textby +1.83 MiB. Routing the hot decode/encode trees through#[inline(never)]non-generic entry points inwaproto::codec(Message,WebMessageInfo, the history-sync records, and the send-pathcompute_size/write_to) collapses the per-crate duplicates into a single instantiation. Net.textregression is now ~+410 KiB (waproto-attributed bloat 3.04 → 1.59 MiB;llvm-lines wacoredropped below the prost baseline).SyncActionValue; the two-pass per-field encode scan on wide messages) and are documented; an earlier CPU regression incollect_unique_index_macsbecame a +28% improvement by decoding each index MAC once and byte-sorting instead of re-walking theMessageFieldchain in the sort comparator.0.8.0, socargo package/ the release workflow no longer reject git-only dependencies.wire.descand voip MLowtables.descbuild scripts now fail the build when the committed.descno longer matches its.proto(the same sha256 guardwaprotoalready had), so a stale descriptor can't silently generate code from an old schema.Test plan
cargo fmt --all— cleancargo sort -w— allCargo.tomlalready sortedcargo clippy --all --tests— 0 warnings, 0 errorscargo test --workspace --exclude e2e-tests— 1,889 tests pass, 0 failures.text~+410 KiB, perf net +25%.proto, passes cleanKnown trade-off: closed enums drop unknown wire values
buffa generates proto2 enum fields as a closed
Option<Enum>and drops any wire value outside the compiled schema at decode time (from_i32 -> None), where prost kept an openOption<i32>that round-tripped any integer. In practice WhatsApp only sends in-schema enum values today, so this is a forward-compatibility gap rather than an active bug, but it means an unknown enum value would vanish on a decode->re-encode path (own-device echo, history-sync persistence) and a hypothetical out-of-rangeSyncdMutation.operationwould count as SET in an ltHash (recoverable via resync).buffa already ships the open-enum runtime type (
EnumValue<E>=Known(E)/Unknown(i32)), so the fix is an opt-in codegen flag rather than new machinery — tracked upstream at anthropics/buffa#269. We keep the closed-enum output for now and will switch the affected fields toEnumValueonce that option lands, rather than hand-rollingint32fields here and reverting the typed-enum ergonomics this PR is about.